﻿#include "../../includes/scripts/cluamanager.h"
#include "../../includes/QsLog/QsLog.h"
#include "../../includes/common/common.h"
#include "../../extends/debuger/ldb.h"

extern "C"
{
    #include "../../extends/lua5.4/lua.h"
    #include "../../extends/lua5.4/lauxlib.h"
    #include "../../extends/lua5.4/lualib.h"
}

#include <QDir>
#include <QDateTime>
#include <qDebug>
#include <QLibrary>
#include <QFileInfo>
#include <QVector>
#include <QWebSocket>

typedef void (*reg_fun)(CLuaManager *g_luamanager,lua_State *g_state);

lua_State *m_lua_state = NULL;
ldb_t     *m_lua_ldb = NULL;

initialiseSingleton(CLuaManager);

const char *scriptcontent = "function _G.reloadstring(scriptPath,scriptString)"
                            "	package.loaded[scriptPath] = nil;"
                            "	local func, err = load(scriptString);"
                            "   package.loaded[scriptPath] = pcall(func);"
                            "end "
                            "function loadscriptstring(scriptpath,scriptstring)"
                            "	reloadstring(scriptpath,scriptstring);"
                            "end";

void reload(lua_State *state, const char *file) {
    QLOG_INFO()<< "ldb reload: " << file;
}

QByteArray readfile(QString filepath)
{
    QByteArray fileBytes;

    QFile pfile;
    pfile.setFileName(filepath);

    if(pfile.open(QIODevice::ReadOnly))
    {
        fileBytes = pfile.readAll();
        pfile.close();
    }

    return fileBytes;
}

CLuaManager::CLuaManager(QObject *parent)
    : QObject(parent)
{
    m_SystemWorkingPathTimer = new QTimer(this);
    m_SystemWorkingPathTimer->setSingleShot(true);

    m_WebSocketServer.SetNetworkFrameManager(this);

    connect(m_SystemWorkingPathTimer, SIGNAL(timeout()), this, SLOT(WorkingdirectoryChanged()));
    connect(&m_pSystemWorkingPathWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(WorkingdirectoryChange(QString)));
}

CLuaManager::~CLuaManager()
{
    clean_system();
    m_WebSocketServer.CloseServer();

    delete m_SystemWorkingPathTimer;
    m_SystemWorkingPathTimer = NULL;
}

/**
 * @brief CLuaManager::SendLog 发送日志信息
 * @param msg 要发送的日志信息
 * @param file 日志所在文件
 * @param line 日志所在行
 * @param type 日志类型
 */
void CLuaManager::SendLog(const char *msg, const char *file, int line, const char *type)
{
    if(msg == NULL || file == NULL || type == NULL)
        return;

    QString content = QString::asprintf("[<%s>%s %s:%d] %s",
                                        type,
                                        QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss").toStdString().c_str(),
                                        file,
                                        line,
                                        msg);

    qDebug()<<content;

    QJsonObject mesObjReturn;
    mesObjReturn["mesid"] = IDD_LUA_MESSAGE_LOG;
    mesObjReturn["type"] = type;
    mesObjReturn["content"] = content;

    m_WebSocketServer.SendAll(JsonToString(mesObjReturn));
}

/**
 * @brief CLuaManager::setCurrentWorkingPath 设置当前系统工作目录
 * @param ppath 要设置的工作目录
 */
void CLuaManager::setCurrentWorkingPath(QString ppath)
{
    //判断路径是否存在
    QDir dir(ppath);
    if(!dir.exists())
    {
        return;
    }

    m_currentWorkingPath = ppath;
    m_pSystemWorkingPathWatcher.addPath(ppath);
}

void CLuaManager::WorkingdirectoryChange(const QString &path)
{
    //判断路径是否存在
    QDir dir(path);
    if(!dir.exists())
    {
        return;
    }

    m_SystemWorkingPathTimer->start(2000);
}

void CLuaManager::WorkingdirectoryChanged(void)
{
    lua_loadscriptstring_callback("scripts\\main",
                                  readfile(m_currentWorkingPath+"/main.lua"));
    lua_setup_callback();
}

/**
 * @brief CLuaManager::Convertpackagetofolder 将指定的包数据转换到指定的文件夹
 * @param packageData 要转换的包数据
 * @param filePath 目标目录
 * @return 如果转换成功返回真，否则返回假
 */
bool CLuaManager::Convertpackagetofolder(QByteArray packageData,QString filePath)
{
    if(packageData.isEmpty() || filePath.isEmpty())
        return false;

    QDir dir;
    dir.setPath(filePath);
    dir.removeRecursively();

    QByteArray tempData = packageData;

    tagSVNVersionPathStruct ptagSVNVersionPathStruct;
    memset(&ptagSVNVersionPathStruct,0,sizeof(tagSVNVersionPathStruct));

    memcpy(&ptagSVNVersionPathStruct,tempData.data(),sizeof(tagSVNVersionPathStruct));
    tempData.remove(0,sizeof(tagSVNVersionPathStruct));

    if(ptagSVNVersionPathStruct.tag[0] != 'L' ||
       ptagSVNVersionPathStruct.tag[1] != 'U' ||
       ptagSVNVersionPathStruct.tag[2] != 'A')
        return false;

    tempData = qUncompress(tempData);

    for(int i=0;i<ptagSVNVersionPathStruct.fileCount;i++)
    {
        tagSVNVersionFileStruct ptagSVNVersionFileStruct;
        memset(&ptagSVNVersionFileStruct,0,sizeof(ptagSVNVersionFileStruct));

        memcpy(&ptagSVNVersionFileStruct,tempData.data(),sizeof(tagSVNVersionFileStruct));
        tempData.remove(0,sizeof(tagSVNVersionFileStruct));

        QString pfileName = QString(ptagSVNVersionFileStruct.fileName);
        QString ptmpFilePath = filePath + pfileName;

        QString tmpFileDirPath = ptmpFilePath.mid(0,ptmpFilePath.lastIndexOf(tr("/")));

        QDir dir(tmpFileDirPath);
        if(!dir.exists())
        {
            if(!dir.mkpath(tmpFileDirPath))
            {
                return false;
            }
        }

        QFile precvFile(ptmpFilePath);
        if(precvFile.open(QIODevice::WriteOnly))
        {
            QByteArray tmpFileData = tempData.mid(0,ptagSVNVersionFileStruct.fileSize);
            tempData.remove(0,ptagSVNVersionFileStruct.fileSize);

            precvFile.write(tmpFileData);
            precvFile.close();
        }
    }

    return true;
}

/**
 * @brief CLuaManager::init_system 初始化系统
 * @param workingpath 系统工作目录
 * @param regdllpath 注册的脚本dll所在目录
 * @param serverport 要打开的服务器端口
 *
 * @return 如果系统初始化成功返回真，否则返回假
 */
bool CLuaManager::init_system(QString workingpath,QString regdllpath,int serverport)
{
    if(workingpath.isEmpty() || regdllpath.isEmpty())
        return false;

    // 设置工作目录
    this->setCurrentWorkingPath(workingpath);

    // 设置注册脚本所在目录
    this->setRegisterDllPath(regdllpath);

    // 是否启用网络
    if(serverport > 0)
        m_WebSocketServer.OpenServer(serverport);

    m_lua_state = luaL_newstate();
    luaL_openlibs(m_lua_state);
    luaopen_cjson(m_lua_state);

    //lua_register(g_lua_state, "c_break", c_break);

    m_lua_ldb = ldb_new(m_lua_state, reload); 

    if(!exe_lua_string(scriptcontent,strlen(scriptcontent)))
        return false;

    // 加载脚本并执行
    WorkingdirectoryChanged();

    reg_all2luas();

    QLOG_INFO() << "init_system init success.";
    SendLog("init_system init success.", __FILE__, __LINE__,"info");

    return true;
}

/**
 * @brief CLuaManager::reg_all2luas 注册所有用到的函数
 */
void CLuaManager::reg_all2luas(void)
{
    if(m_lua_state == NULL || m_currentRegisterDllPath.isEmpty())
        return;

    QVector<QString> pfilelist;
    if(FindFile(m_currentRegisterDllPath,pfilelist) < 0)
        return;

    for(int i=0;i<pfilelist.size();i++)
    {
        QFileInfo pFileInfo(pfilelist[i]);
        if(!pFileInfo.exists() || pFileInfo.suffix().toLower() != "dll")
            continue;

        QLibrary pDllLibrary(pfilelist[i]);
        if(!pDllLibrary.load())
        {
            QLOG_ERROR() << "load dll:"<<pfilelist[i]<<" fail.";
            SendLog("load dll fail.", __FILE__, __LINE__,"error");

            continue;
        }

        reg_fun reg_xxx2Luas = (reg_fun)pDllLibrary.resolve("reg_xxx2Luas");
        if(reg_xxx2Luas)
        {
            reg_xxx2Luas(this,m_lua_state);
        }

        //pDllLibrary.unload();
    }
}

/**
 * @brief CLuaManager::clean_system 卸载系统
 */
void CLuaManager::clean_system(void)
{
    ldb_closesocket();

    if (m_lua_state != NULL)
    {
        ldb_free(m_lua_ldb);
        m_lua_ldb=NULL;

        lua_close(m_lua_state);
        m_lua_state = NULL;
        QLOG_INFO() << "clean_system close success.";
        SendLog("clean_system close success.", __FILE__, __LINE__,"info");
    }
}

/**
 * @brief CLuaManager::exe_lua_string 执行指定的lua脚本
 * @param luastring 要执行的lua脚本
 * @param size 脚本字段长度
 * @return 如果脚本执行成功返回真，否则返回假
 */
bool CLuaManager::exe_lua_string(const char* luastring,quint32 size)
{
    if(m_lua_state == NULL || luastring == NULL || size <= 0)
        return false;

    if (luaL_loadbuffer(m_lua_state, luastring, size, "qtCore") ||
            lua_pcall(m_lua_state, 0, 0, 0))
    {
        // 发生错误时
        const char* pErrorMsg = luaL_checkstring(m_lua_state, -1);
        QLOG_ERROR() << "exe_lua_string:" << pErrorMsg;
        SendLog(pErrorMsg, __FILE__, __LINE__,"error");
        return false;
    }

    QLOG_INFO() << "exe_lua_string success.";
    SendLog("lua load success.", __FILE__, __LINE__,"info");

    return true;
}

/**
 * @brief CLuaManager::lua_loadscriptstring_callback 从字符串中导入脚本
 * @param scriptpath 脚本所在目录
 * @param scriptstring 脚本字符串
 */
void CLuaManager::lua_loadscriptstring_callback(const char* scriptpath, const char* scriptstring)
{
    if (m_lua_state == NULL ||
            scriptpath == NULL ||
            scriptstring == NULL)
        return;

    int iRet = 0;

    lua_getglobal(m_lua_state, "loadscriptstring");
    lua_pushstring(m_lua_state, scriptpath);
    lua_pushstring(m_lua_state, scriptstring);
    iRet = lua_pcall(m_lua_state, 2, 0, 0);

    if (iRet)
    {
        const char *pErrorMsg = lua_tostring(m_lua_state, -1);
        QLOG_ERROR() << "lua_loadscript_callback:" << pErrorMsg;
        SendLog(pErrorMsg, __FILE__, __LINE__, "error");
    }
    else
    {
        QLOG_INFO() << "lua_loadscript_callback successed.";
        SendLog("lua_loadscript_callback successed.", __FILE__, __LINE__, "info");
    }
}

/**
 * @brief CLuaManager::lua_setup_callback 初始脚本系统
 */
void CLuaManager::lua_setup_callback(void)
{
    if(m_lua_state == NULL)
        return;

    lua_getglobal(m_lua_state, "main");
    if (lua_istable(m_lua_state, -1) || lua_type(m_lua_state, -1) == LUA_TTABLE)
    {
        lua_getfield(m_lua_state, -1, "setup");

        if (lua_isnil(m_lua_state, -1) || !lua_isfunction(m_lua_state, -1))
        {
            SendLog("lua load 'setup' fail.", __FILE__, __LINE__,"info");
            return;
        }

        int iRet = lua_pcall(m_lua_state, 0, 0, 0);
        if (iRet)
        {
            const char *pErrorMsg = lua_tostring(m_lua_state, -1);
            QLOG_ERROR() << "lua_setup_callback:" << pErrorMsg;
            SendLog(pErrorMsg, __FILE__, __LINE__,"error");
        }
    }
}

/**
 * @brief CLuaManager::lua_network_callback 网络消息处理
 * @param socketid 客户端的名称
 * @param msg 要处理的消息，这里以json格式组织消息
 */
/*void CLuaManager::lua_network_callback(QString socketid, QString msg)
{
    if (m_lua_state == NULL || socketid.isEmpty() || msg.isEmpty())
        return;

    int iRet = 0;

    lua_getglobal(m_lua_state, "main");
    if (lua_istable(m_lua_state, -1) || lua_type(m_lua_state, -1) == LUA_TTABLE)
    {
        //lua_pushstring(m_lua_state, "process_network");
        //lua_gettable(m_lua_state, -2);

        lua_getfield(m_lua_state, -1, "process_network");

        if (lua_isnil(m_lua_state, -1) || !lua_isfunction(m_lua_state,-1))
        {
            SendLog("lua load 'process_network' fail.", __FILE__, __LINE__,"info");
            return;
        }

        lua_pushnil(m_lua_state);
        lua_pushstring(m_lua_state, socketid.toStdString().c_str());
        lua_pushstring(m_lua_state, msg.toStdString().c_str());

        iRet = lua_pcall(m_lua_state, 3, 0, 0);

        if (iRet)
        {
            const char *pErrorMsg = lua_tostring(m_lua_state, -1);
            QLOG_ERROR() << "lua_network_callback:" << pErrorMsg;
            SendLog(pErrorMsg, __FILE__, __LINE__,"error");
        }
    }
}*/

/**
 * @brief CLuaManager::OnProcessConnectedNetMes 处理一个新的连接到达
 * @param conn 到达的新的连接
 */
void CLuaManager::OnProcessConnectedNetMes(QWebSocket *conn)
{

}

/**
 * @brief CLuaManager::OnProcessDisconnectedNetMes 处理一个连接关闭
 * @param conn 要断开的连接
 */
void CLuaManager::OnProcessDisconnectedNetMes(QWebSocket *conn)
{

}

/**
 * @brief CLuaManager::OnProcessNetBinary 处理网络二进制消息
 * @param conn 要处理的客户端
 * @param data 到达的二进制消息
 */
void CLuaManager::OnProcessNetBinary(QWebSocket *conn,QByteArray &data)
{
    tagVersionOper pVersionOper;
    QByteArray reciverData = data;

    memset(&pVersionOper,0,sizeof(tagVersionOper));
    memcpy(&pVersionOper,reciverData.data(),sizeof(tagVersionOper));
    reciverData.remove(0,sizeof(tagVersionOper));

    switch(pVersionOper.tagIndex)
    {
    case IDD_LUA_MESSAGE_UPDATELUA:
    {
        Convertpackagetofolder(reciverData,m_currentWorkingPath);
    }
        break;
    default:
        break;
    }
}

/**
 * @brief CLuaManager::OnProcessNetText 处理网络字符串消息
 * @param conn 要处理的客户端
 * @param mes 到达的字符串消息
 */
void CLuaManager::OnProcessNetText(QWebSocket *conn,QString mes)
{

}

